/*
 * renderloop.cpp
 *
 *  Created on: 10.09.2011
 *      Author: marcell
 */

#include <gfx/camera.h>
#include <gfx/projector.h>
#include <gfx/renderenvironment.h>
#include <gfx/gl20/device.h>
#include <gfx/gl20/renderloop.h>
#include <scene/light.h>
#include <scene/renderbucket.h>
#include <scene/scene.h>
#include <utils/cameraprojectorsetup.h>
#include <utils/nodescanner.h>
#include <GL/glew.h>
#include <vfs.h>

ceRenderLoop::ceRenderLoop ()
	: _viewport (0)
	, _renderTarget (0)
	, _shadowOverlayer(0)
	, _performUpdate (true)
  , _shadowEnabled(true)
{
	_shadowMap[0] = 0;
	_shadowMap[1] = 0;
	_shadowMap[2] = 0;
}

ceRenderLoop::~ceRenderLoop ()
{
}

void ceRenderLoop::PerformUpdate (bool performUpdate)
{
	_performUpdate = performUpdate;
}

bool ceRenderLoop::IsPerformUpdate () const
{
	return _performUpdate;
}

bool ceRenderLoop::Initialize(iDevice *device)
{
  ceRenderTargetDescriptor desc;
	desc.Size = ceRect (512, 512);
	desc.ColorFormat  = PF_R8G8B8A8;
  desc.DepthStencilTexture = true;
	desc.DepthStencilFormat = PF_D24S8;
	_shadowMap[0] = device->CreateRenderTarget(desc);

	desc.Size = ceRect (256, 256);
	desc.ColorFormat  = PF_R8G8B8A8;
  desc.DepthStencilTexture = true;
	desc.DepthStencilFormat = PF_D24S8;
	_shadowMap[1] = device->CreateRenderTarget(desc);

	desc.Size = ceRect (128, 128);
	desc.ColorFormat  = PF_R8G8B8A8;
  desc.DepthStencilTexture = true;
	desc.DepthStencilFormat = PF_D24S8;
	_shadowMap[2] = device->CreateRenderTarget(desc);

  desc.Size = ceRect (1, 1);
  desc.ColorFormat  = PF_R8G8B8A8;
  desc.DepthStencilTexture = true;
	desc.DepthStencilFormat = PF_D24S8;
  _noShadowMap = device->CreateRenderTarget(desc);

  return _shadowMap[0] != 0
      && _shadowMap[1] != 0
      && _shadowMap[2] != 0
      && _noShadowMap != 0;
  return true;
}

void ceRenderLoop::SetShadowMapDistances(float d0, float d1, float d2)
{
	_shadowMapDistances[0] = d0;
	_shadowMapDistances[1] = d1;
	_shadowMapDistances[2] = d2;
}

void ceRenderLoop::SetViewport(iDevice* device, iViewport *viewport)
{
  fflush(stdout);
	_viewport = viewport;
  if (    !_renderTarget
			||  _renderTarget->GetWidth() != _viewport->GetWidth()
			||  _renderTarget->GetHeight() != _viewport->GetHeight()
			)
		{
      if (_renderTarget)
        {
          _renderTarget->Release();
        }
      ceRenderTargetDescriptor desc;
			desc.Size                 = ceRect (viewport->GetWidth(), viewport->GetHeight());
			desc.ColorFormat          = PF_R8G8B8A8;
			desc.DepthStencilTexture  = false;
      desc.DepthStencilFormat   = PF_D24;

			_renderTarget = device->CreateRenderTarget(desc);
		}

	if (    !_shadowOverlayer
      ||  _shadowOverlayer->GetWidth() != _viewport->GetWidth() >> 1
      ||  _shadowOverlayer->GetHeight() != _viewport->GetHeight() >> 1)
		{
      if (_shadowOverlayer)
        {
          _shadowOverlayer->Release();
        }
      ceRenderTargetDescriptor desc;
      desc.Size                 = ceRect (viewport->GetWidth() >> 1, viewport->GetHeight() >> 1);
			desc.ColorFormat          = PF_R8G8B8A8;
			desc.DepthStencilTexture  = false;
      desc.DepthStencilFormat   = PF_D24;
			_shadowOverlayer = device->CreateRenderTarget(desc);
    }

}

const iViewport* ceRenderLoop::GetViewport() const
{
	return _viewport;
}

void ceRenderLoop::SetShadowEnabled(bool shadowEnabled)
{
  _shadowEnabled = shadowEnabled;
}


bool ceRenderLoop::IsShadowEnabled() const
{
  return _shadowEnabled;
}

void ceRenderLoop::Initialize()
{
	// TODO: move those instructions into the device
	glFrontFace(GL_CW);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glEnable (GL_DEPTH_TEST);
}

void ceRenderLoop::SetCombineProgram (iProgram* combineProgram)
{
	_combineProgram = combineProgram;
}

void ceRenderLoop::RenderShadowMap(iDevice       *device,
																	 ceScene        *scene,
																	 ceCamera       &vpCamera,
																	 ceProjector    &vpProjector,
                                   ceLightObject  *lightObj,
                                   unsigned       frame)
{
	ceVector3f from = lightObj->GetLight()->GetPosition();
	ceVector3f up (0, 0, 1);
	ceVector3f spherePos0 = vpCamera.GetPosInfront(_shadowMapDistances[0]);
	ceVector3f spherePos1 = vpCamera.GetPosInfront(_shadowMapDistances[1]);
	ceVector3f spherePos2 = vpCamera.GetPosInfront(_shadowMapDistances[2]);

	float sphereRadius0 = vpProjector.GetSphereRadius(_shadowMapDistances[0], false);
	float sphereRadius1 = vpProjector.GetSphereRadius(_shadowMapDistances[1], false);
	float sphereRadius2 = vpProjector.GetSphereRadius(_shadowMapDistances[2], false);


	ceSphere sphere[3];
	sphere[0] = ceSphere (spherePos0, sphereRadius0);
	sphere[1] = ceSphere (spherePos1, sphereRadius1);
	sphere[2] = ceSphere (spherePos2, sphereRadius2);

	float maxSphereRadius = vpProjector.GetSphereRadius(sphereRadius2, false);
	float zFar = sphere[2].GetMaxDistance (from);
	float zNear = zFar - maxSphereRadius * 3.0f;
	zFar += maxSphereRadius * 3.0f;
	if (zNear < 1.0)
		{
			zNear = 1.0;
		}

	ceCamera camera[3];
	ceProjector projector[3];
	ceCameraProjectorSetup camSetup;
	camSetup.SetupPerspective(from, up, sphere[0], camera[0], projector[0], zNear, zFar);
	camSetup.SetupPerspective(from, up, sphere[1], camera[1], projector[1], zNear, zFar);
	camSetup.SetupPerspective(from, up, sphere[2], camera[2], projector[2], zNear, zFar);


	ceRenderBucket bucket;
	ceRenderEnvironment env;
	ceNodeScanner scanner(device, &bucket, &env);
	scanner.SetCollectEffectCameras(false);
	scanner.SetCollectLights(false);
	scanner.SetUpdate(false);
  scanner.SetCollectShadowCasters(true, false);
  scanner.Scan(scene, camera[2], projector[2], frame);

	glEnable (GL_DEPTH_TEST);
	glDepthMask(true);
  glColorMask(false, false, false, false);
  for (unsigned i=0; i<3; i++)
		{
			glDisable (GL_BLEND);
			_shadowMapView[i] = camera[i].GetMatrix();
			_shadowMapProjection[i] = projector[i].GetMatrix();

			_smCam[i] = camera[i];
			_smProj[i] = projector[i];

			// render the entire scene in this offscreen viewport
			device->SetRenderTarget(_shadowMap[i]);
			device->SetViewport(_shadowMap[i]);
      device->Clear(false, ceColor4f(0, 0, 0, 0), true, 1.0);
			device->SetProjector(projector[i]);
			device->SetCamera(camera[i]);
      bucket.Render(env, device, RP_Depth, true);
		}
	glColorMask(true, true, true, true);
}

void ceRenderLoop::RenderShadowOverlayer (iDevice				*device,
																					ceRenderBucket	&bucket,
																					ceRenderEnvironment& env,
																					ceCamera				&camera,
																					ceProjector			&projector)
{
	device->SetRenderTarget(_shadowOverlayer);
	device->SetViewport(_shadowOverlayer);
  glDepthMask(true);
  glColorMask (true, true, true, true);
  device->Clear(true, ceColor4f(0, 0, 0, 0), true, 1.0);

	glEnable (GL_DEPTH_TEST);
	device->SetProjector(projector);
	device->SetCamera(camera);

  // and set them in the device
  device->SetShadowMap (0, _shadowMap[0]->GetDepthTexture(), _shadowMapView[0], _shadowMapProjection[0]);
  device->SetShadowMap (1, _shadowMap[1]->GetDepthTexture(), _shadowMapView[1], _shadowMapProjection[1]);
  device->SetShadowMap (2, _shadowMap[2]->GetDepthTexture(), _shadowMapView[2], _shadowMapProjection[2]);


	glDisable (GL_BLEND);
  glDepthFunc(GL_LEQUAL);
  glColorMask (true, true, true, true);
  bucket.RenderSolid(env, device, RP_Shadow, true);
}

void ceRenderLoop::RenderFrame (bool setTxt, iTexture2D* texture)
{
	if (setTxt)
		{
			for (unsigned i=0; i<TS_Count; i++)
				{
					glActiveTexture (GL_TEXTURE0 + i);
					glDisable (GL_TEXTURE_2D);
				}
			glActiveTexture (GL_TEXTURE0);
			texture->Bind ();
		}
#define VERT(x,y) glTexCoord2f((x), (y)); glVertex2f ((x), (y))
	glBegin(GL_TRIANGLES);
	glColor3f (1, 1, 1);
	VERT(0,0);
	VERT(0,1);
	VERT(1,1);

	VERT(0,0);
	VERT(1,1);
	VERT(1,0);
	glEnd();
#undef VERT
}

void ceRenderLoop::Render(iDevice *device, ceScene *scene, ceCamera &camera, ceProjector &projector)
{
	static unsigned frame = 0;

	Initialize();
	ceRenderBucket mainBucket;
	ceRenderEnvironment env;

	ceNodeScanner scanner(device, &mainBucket, &env);
	scanner.SetUpdate(_performUpdate);
  scanner.Scan(scene, camera, projector, frame++);

  device->SetRenderTarget(_noShadowMap);
  device->Clear(false, ceColor4f (0.0f, 0.0f, 0.0f, 1.0f), true, 1.0f);
  ceColor4f clearColor (0.0f, 0.0f, 0.0f, 1.0f);

  device->SetRenderTarget(0);
  device->SetViewport(_viewport);
  device->Clear(true, clearColor, true, 1.0);
  device->SetCamera(camera);
  device->SetProjector(projector);
  // just write to the depth buffer
  glColorMask (false, false, false, false);
  mainBucket.RenderUnlit(env, device, RP_Depth, true);
  mainBucket.RenderSolid(env, device, RP_Depth, true);

  static bool first_run =true;
  if (!_shadowEnabled)
    {
      device->SetShadowMap (0, _noShadowMap->GetDepthTexture(), _shadowMapView[0], _shadowMapProjection[0]);
      device->SetShadowMap (1, _noShadowMap->GetDepthTexture(), _shadowMapView[1], _shadowMapProjection[1]);
      device->SetShadowMap (2, _noShadowMap->GetDepthTexture(), _shadowMapView[2], _shadowMapProjection[2]);
    }


  /* first step render the stuff that is not lit */

  // and go back, writing to the color buffer
  glColorMask (true, true, true, true);
  glDepthFunc(GL_LEQUAL);
  glBlendFunc(GL_ONE, GL_ONE);
  glEnable (GL_BLEND);
  device->SetUseBlend(true);

  device->SetLight(0);
  mainBucket.RenderUnlit(env, device, RP_Diffuse, true);

	_numObjects = mainBucket.GetNumberOfElements();

  for (unsigned i=0, j=mainBucket.GetNumberOfLights(); i<j; i++)
    {
      ceLightObject* light = mainBucket.GetLight(i);
      if (_shadowEnabled)
        {
          if (light->CastsShadow())
            {
              // render the shadow maps
              RenderShadowMap(device, scene, camera, projector, mainBucket.GetLight(i), frame++);
              RenderShadowOverlayer(device, mainBucket, env, camera, projector);
              device->SetShadowOverlayer(_shadowOverlayer->GetColorTexture());
            }
          else
            {
              device->SetShadowOverlayer(0);
            }
        }

      device->SetRenderTarget(0);
      device->SetViewport(_viewport);

      device->SetCamera(camera);
      device->SetProjector(projector);

      // and go back, writing to the color buffer
      glColorMask (true, true, true, true);
      glDepthFunc(GL_LEQUAL);
      glBlendFunc(GL_ONE, GL_ONE);
      glEnable (GL_BLEND);
      device->SetUseBlend(true);

      device->SetLight(light->GetLight());
			mainBucket.RenderSolid(env, device, RP_Diffuse, true);
		}
  first_run = false;
	device->SetUseBlend(false);
	device->ClearShadowMap(0);
	device->ClearShadowMap(1);
	device->ClearShadowMap(2);


/*
  glDisable (GL_BLEND);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0, 2, 0, 2);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity();

  glUseProgram(0);

  RenderFrame (true, _shadowOverlayer->GetColorTexture());
*/
	return;
}


void ceRenderLoop::RenderViewFrustum (const ceCamera& cam, const ceProjector& proj)
{
	ceVector3f p0 (0, 0, 0);
	ceVector3f pnlb (proj.GetLeft (), proj.GetBottom (), -proj.GetNear ());
	ceVector3f pnrb (proj.GetRight (), proj.GetBottom (), -proj.GetNear ());
	ceVector3f pnlt (proj.GetLeft (), proj.GetTop(), -proj.GetNear ());
	ceVector3f pnrt (proj.GetRight (), proj.GetTop(), -proj.GetNear ());

	float a = proj.GetFar () / proj.GetNear ();
	ceVector3f pflb = pnlb * a;
	ceVector3f pfrb = pnrb * a;
	ceVector3f pflt = pnlt * a;
	ceVector3f pfrt = pnrt * a;

	ceMatrix4f M = cam.GetMatrix().Inverted ();
	p0 = M * p0;
	pnlb = M * pnlb;
	pnrb = M * pnrb;
	pnlt = M * pnlt;
	pnrt = M * pnrt;
	pflb = M * pflb;
	pfrb = M * pfrb;
	pflt = M * pflt;
	pfrt = M * pfrt;

#define LINE(p0, p1) glVertex3f(p0.x, p0.y, p0.z); glVertex3f (p1.x, p1.y, p1.z)
#define RING(p0, p1, p2, p3) LINE(p0, p1); LINE(p1, p2); LINE(p2, p3); LINE(p3, p0)

	glDisable (GL_LIGHTING);
	glBegin (GL_LINES);
		glColor4f (1, 1, 1, 1);
		LINE(pnlt, pflt);
		LINE(pnrt, pfrt);
		LINE(pnlb, pflb);
		LINE(pnrb, pfrb);
		RING(pnlt, pnrt, pnrb, pnlb);
		RING(pflt, pfrt, pfrb, pflb);
	glEnd ();
}
